﻿using RockBus.BufferManager.Interfaces;
using RockBus.DataContracts;
using RockBus.Logging.Interfaces;
using RockBus.MessageRepository.Interfaces;
using RockBus.Messages.Interfaces;
using RockBus.ServiceModel;
using RockBus.Utilities;
using System;
using System.Reflection;
using System.ServiceModel.Channels;
using System.Threading.Tasks;
using System.Xml;

namespace RockBus.Transport.Db
{
    public struct MessageData
    {
        public long messageId;
        public int fragmentCount;
        public int size;
    }

    public interface IMessageRepositoryHelper
    {
        int PollingIntervalMs { get; }

        MessageData StoreMessage(IMessage message, Guid correlationId);

        void CompleteMessage(MessageData md);

        void EnqueueEntry(long messageId, RoutingHeader routingHeader);

        void RollbackEntry(long queueEntryId, Exception exception);

        void CompleteEntry(long queueEntryId);

        void CreateErrorMessage(long messageId, byte[] routingHeaderByteArray, int deliveryAttemptCount, Exception exception);

        Task<DequeueResult> StartDequeueEntries();

        byte[] GetMessageFragment(long messageId, int chunkIndex);
    }

    public class MessageRepositoryHelper : IMessageRepositoryHelper
    {
        public MessageRepositoryHelper(
            ILogger logger,
            IMessageRepository messageRepository,
            IDbTransportConfiguration configuration,
            IBufferManager bufferManager)
        {
            this.Logger = logger;
            this.MessageRepository = messageRepository;
            this.Configuration = configuration;
            this.BufferManager = bufferManager;

            if (this.BufferManager.Configuration.MaxBufferSize < this.Configuration.MessageFragmentSize)
            {
                this.Logger.LogWarning(MethodBase.GetCurrentMethod(), "BufferManager uses {0} for MaxBufferSize; Yet MessageFragmentSize is {1}. Check the configuration settings to make sure settings are optimal.",
                    this.BufferManager.Configuration.MaxBufferSize, this.Configuration.MessageFragmentSize);
            }
        }

        private ILogger Logger { get; set; }

        private IMessageRepository MessageRepository { get; set; }

        private IDbTransportConfiguration Configuration { get; set; }

        private IBufferManager BufferManager { get; set; }

        public MessageData StoreMessage(IMessage message, Guid correlationId)
        {
            string receiveAddress = (string)message.Properties.GetReceiveAddress();
            if (null == receiveAddress)
            {
                receiveAddress = string.Empty;
            }

            var md = new MessageData();
            md.messageId = this.MessageRepository.CreateMessage(
                correlationId.ToString(),
                message.GetMessageVersionAsString(),
                message.Action,
                DateTime.UtcNow,
                receiveAddress);

            using (var wcs = new WritableChunkingStream(this.Logger, this.BufferManager, this.Configuration.MessageFragmentSize, (buffer, offset, count, index) =>
            {
                // Assuming we always receive offset 0.
                this.MessageRepository.CreateMessageFragment(md.messageId, index, buffer, count);
                md.fragmentCount = index + 1;
                md.size += count;
            }))
            using (var writer = XmlWriter.Create(wcs))
            {
                message.Message.WriteMessage(writer);
            }

            return md;
        }

        public int PollingIntervalMs { get { return this.Configuration.PollingIntervalMs; } }

        public void CompleteMessage(MessageData messageData)
        {
            this.MessageRepository.CompleteMessage(messageData.messageId, messageData.fragmentCount, messageData.size);
        }

        public void EnqueueEntry(long messageId, RoutingHeader routingHeader)
        {
            this.MessageRepository.EnqueueEntry(
                messageId,
                routingHeader.GetByteArray(),
                0,
                false,
                routingHeader.ProcessorInfo.MachineName,
                routingHeader.ProcessorInfo.ProcessId,
                routingHeader.ProcessorInfo.AppDomainId,
                routingHeader.SubscriptionDetail.MessageValidityDurationSecs,
                routingHeader.SubscriptionDetail.DeliveryAttemptIntervalSecs ?? this.Configuration.DefaultDeliveryAttemptIntervalSecs,
                routingHeader.SubscriptionDetail.MaxDeliveryAttemptCount ?? this.Configuration.DefaultMaxDeliveryAttemptCount,
                routingHeader.ProcessingData.IsResponse);
        }

        public void RollbackEntry(long queueEntryId, Exception exception)
        {
            this.MessageRepository.RollbackEntry(queueEntryId, exception);
        }

        public void CompleteEntry(long queueEntryId)
        {
            this.MessageRepository.CompleteEntry(queueEntryId);
        }

        public void CreateErrorMessage(long messageId, byte[] routingHeaderByteArray, int deliveryAttemptCount, Exception exception)
        {
            this.MessageRepository.CreateErrorMessage(
                messageId,
                routingHeaderByteArray,
                ProcessorInfo.DefaultMachineName,
                ProcessorInfo.DefaultProcessId,
                ProcessorInfo.DefaultAppDomainId,
                deliveryAttemptCount,
                exception);
        }

        public Task<DequeueResult> StartDequeueEntries()
        {
            return this.MessageRepository.StartDequeueEnties(
                        this.Configuration.PollingBatchSize,
                        ProcessorInfo.DefaultMachineName,
                        ProcessorInfo.DefaultProcessId,
                        ProcessorInfo.DefaultAppDomainId,
                        ProcessorInfo.DefaultProcessorId);
        }

        public byte[] GetMessageFragment(long messageId, int partIndex)
        {
            return this.MessageRepository.GetMessageFragment(messageId, partIndex);
        }
    }
}